#!/bin/bash

# This is not a script it's a module. The above line helps with content-type identification.

# Stash Initialization Must be done globally to avoid "pipe collisions"
###########################################################################
declare httpStashDescriptor;

for httpStashDescriptor in /dev/fd/{100..500}; do
    [[ ! -e "$httpStashDescriptor" ]] && { break; }
done

httpStashDescriptor=${httpStashDescriptor##*/};

eval "exec $httpStashDescriptor<> >(:);";

###########################################################################


http.std.get() { #uri

    local host=`http.parse.uri host "$1"` || return;
    local path=`http.parse.uri path "$1"` || return;

    local query=`http.parse.uri query` && query='?'"$query";

    echo -en "GET ${path}${query} HTTP/1.1\nhost: ${host}\nconnection: close\n\n"

}

http.std.post() { #host/path/to/resource?querystr #content=stdin
    echo -en "POST /${1#*/} HTTP/1.1\nhost: ${1%%/*}\n\n"; cat;
}

http.stash.header() { # write the header to a dynamic pipe for later retrieval via http.raid.stash

    local response;

    while read -r response; do # save the header to runtime pipe
        
        response="${response//$'\r'/}";

        [[ "$response" = '' ]] && break;

        echo "$response" >&$httpStashDescriptor;

    done;

    echo $'\30' >&$httpStashDescriptor;

    cat; # dump the content to stdout

}

http.raid.stash() {
    
    local TMOUT=0.04;

    # TMOUT is to prevent a hang if called with no header present.
    # on an over tasked machine this could be a problem.
    # increase TMOUT to a more reasonable value if needed
    # or comment it out.

    read -u $httpStashDescriptor -d $'\30' "$@";

    [[ $# -eq 0 ]] && echo -n "$REPLY";

}

http.parse.uri() {

    # $1 = scheme | host | port | path | query | param | file
    # $2 = URI
    # $3 variable label to write result if not present the result is echoed to stdout.

    # param has its own convention: $3 = param name; $4 = variable label

    local -i invalid_arg=1 empty_uri=2 missing_component=3 missing_param=4 malformed_request=5;

    # enforce minimum argument length
    [[ $# -lt 2 ]] && return $invalid_arg;

    # assert uri value
    [[ -z "$2" ]] && return $empty_uri;

    local buffer;

    case $1 in
	scheme)

	    # assert we have a scheme
	    [[ "$2" =~ ^.+'://' ]] || return $missing_component;

	    buffer=${2%://*};    # remove anything unrelated to scheme

	    ;;

	host)

	    buffer=${2#*//};      # remove scheme
	    buffer=${buffer%%/*}; # remove path components
	    buffer=${buffer%%:*}; # remove a possible port number

	    # assert we have a host
	    [[ -z "$buffer" ]] && return $missing_component;

	    ;;

	port)

	    buffer=${2#*://};     # remove scheme
	    buffer=${buffer%%/*}; # remove path components

	    # assert we have a port
	    [[ "$buffer" =~ ^.+: ]] || return $missing_component;

	    buffer=${buffer##*:}; # remove host components

	    ;;

	path)

	    buffer=${2#*//};      # remove scheme
	    buffer=${buffer%%\?*} # remove query

	    #make default path if there is no path component ie: host.com -> host.com/
	    [[ "$buffer" =~ / ]] || { buffer=$buffer/; }

	    buffer=/${buffer#*/}  # remove host components, restore root slash;

	    ;;

	query)

	    buffer=${2##*\?}      # remove all prior components
	    ;;

	param)

	    # in the case of param we require 3 minimum arguments
	    [[ $# -lt 3 ]] && return $invalid_arg;

	    # if variable assignment is desired it is now the fourth argument
	    # which means we must do assignment here and return early later on

	    buffer=${2##*\?};    # remove all non query components
	    
	    # assert we have a query
	    [[ -z "$buffer" ]] && return $missing_component; # we have no query data

	    # assert the param is present
	    [[ "$buffer" =~ "$3=" ]] || return $missing_param;

	    buffer=${buffer##$3=}; # remove the identifier and value operator
	    buffer=${buffer%%&*};  # remove remaining params if any

	    [[ $# -gt 3 ]] && {    # perform assignment
		eval "$4='$buffer'";
		return;
	    }

	    echo "$buffer";
	    return;

	    ;;

	file)

	    buffer=${2%%\?*};      # remove query
	    buffer=${buffer##*/};  # remove path stuff

	    # assert we have a file or directory
	    [[ -z "$buffer" ]] && return $missing_component;

	    ;;

	*)
	    return $malformed_request;
	    ;;
    esac
    
    [[ $# -eq 3 ]] && {
	eval "$3='$buffer'";
    } || {
	echo "$buffer";
    }

}

http.drop.header() {

    local response;

    while read -r response; do # drop the header information
        
        response="${response//$'\r'/}";

        [[ "$response" = '' ]] && break;

    done;

    cat; # dump content to stdout

}

